#include "Node.h"

#include "InnerNode.h"
#include "Leaf.h"


/**
 * Fügt ein Element in den Teilbaum ein. Ist das Einfügen erfolgreich, werden alle Schreiblocks innerhalb dieses Teilbaums wieder freigegeben.
 */
bool Node::insert(char* key, TID tid)
{
    // Wenn man hier drin ist, befindet man sich im RootKnoten.
    SplitOp so = this->insertExt(key, tid);

    // Alter Root musste sich splitten => Neuer Root mit zwei Kindern
    if (so.splitPerformed) {
        vector<unsigned> res = context.segment.requestPages(1,1);
        unsigned newRootId = res[0];

        BufferFrame& page = context.segment.getPage(newRootId, true);

        // Header erzeugen
        InnerNode newRoot = InnerNode::fromNew(page, context);

        NodeHeader& header = *((NodeHeader*)(page.getData()));
        header.entryCount = 1;

        char* offset = ((char*)(page.getData())) + sizeof(NodeHeader);
        *((unsigned*)offset) = this->segmentPageId;
        offset += sizeof(unsigned);
        memcpy(offset, so.key, context.keyLen);
        free(so.key);
        offset += context.keyLen;
        *((unsigned*)offset) = so.newPageId;

        this->segmentPageId = newRootId;
        this->tmpPage = NULL;

        context.segment.unfixPage(page, true);

    }
    return true;
}

SplitOp Node::insertExt(char* key, TID tid)
{
    // TODO: Wenn der betreffende Knoten nicht verändert wurde, dann soll er beim unfix nicht als dirty markiert werden.

    SplitOp result;
    result.splitPerformed = false;

    // Page mit Schreibsperre öffnen
    this->tmpPage = &(context.segment.getPage(this->segmentPageId, true));
    NodeHeader& header = *((NodeHeader*)((*(this->tmpPage)).getData()));

    // Blattknoten
    if (header.isLeaf)
    {
        Leaf leaf = Leaf::fromExisting(*(this->tmpPage), context);
        return leaf.insert(key, tid);
    }

    // Innerer Knoten
    InnerNode innerNode = InnerNode::fromExisting(*(this->tmpPage), context);

    // Genug Platz, dass Unterknoten aufgesprlittet werden könnten?
    if (innerNode.hasEnoughSpaceForNewChild())
    {
        // Lock auf Elternelemente auflösen, da alle Ereignisse im Teilbaum behandelt werden können
        this->releaseParentsWriteLock();
    }
    return innerNode.insert(key, tid);
}


RemovalResult* Node::remove(char* key, bool isRoot)
{   // Page mit Schreibsperre öffnen
    this->tmpPage = &(context.segment.getPage(this->segmentPageId, true));
    NodeHeader& header = *((NodeHeader*)((*(this->tmpPage)).getData()));

    if (header.isLeaf)
    {   // Blattknoten
        Leaf leaf = Leaf::fromExisting(*(this->tmpPage), context);
        RemovalResult* result = leaf.remove(key, isRoot);

        context.segment.unfixPage(*(this->tmpPage), result->removed);
        this->tmpPage = NULL;
        return result;
    }

    else
    {   // Innerer Knoten
        InnerNode innerNode = InnerNode::fromExisting(*(this->tmpPage), context);
        RemovalResult* result = innerNode.remove(key, isRoot);

        context.segment.unfixPage(*(this->tmpPage), result->removed);
        this->tmpPage = NULL;
        return result;
    }
}

char* Node::getLeftKey()
{   // Page mit Lesesperre öffnen
    this->tmpPage = &(context.segment.getPage(this->segmentPageId, false));
    NodeHeader& header = *((NodeHeader*)((*(this->tmpPage)).getData()));
    char* key;

    if (header.isLeaf)
    {   // Blattknoten
        key = new char[context.keyLen];
        memcpy(key, (char*)this->tmpPage->getData() + sizeof(NodeHeader), context.keyLen);
    }

    else
    {   // Innerer Knoten
        unsigned* childId = (unsigned*)((char*)this->tmpPage->getData() + sizeof(NodeHeader));
        Node child(*childId, context);
        key = child.getLeftKey();
    }
    context.segment.unfixPage(*(this->tmpPage), false);
    this->tmpPage = NULL;
    return key;
}


TID Node::lookup(char* key)
{   // Page mit Lesesperre öffnen
    this->tmpPage = &(context.segment.getPage(this->segmentPageId, false));
    NodeHeader& header = *((NodeHeader*)((*(this->tmpPage)).getData()));

    if (header.isLeaf)
    {   // Blattknoten
        Leaf leaf = Leaf::fromExisting(*(this->tmpPage), context);
        TID result = leaf.lookup(key);

        context.segment.unfixPage(*(this->tmpPage), false);
        this->tmpPage = NULL;
        return result;
    }

    else
    {   // Innerer Knoten
        InnerNode innerNode = InnerNode::fromExisting(*(this->tmpPage), context);
        TID result = innerNode.lookup(key);

        context.segment.unfixPage(*(this->tmpPage), false);
        this->tmpPage = NULL;
        return result;
    }
}



/**
 * Es muss gelten: key1 < key2
 */
index_iterator Node::lookupRange(char* lower, char* upper)
{   // Page mit Lesesperre öffnen
    this->tmpPage = &(context.segment.getPage(this->segmentPageId, false));
    NodeHeader& header = *((NodeHeader*)((*(this->tmpPage)).getData()));

    if (header.isLeaf)
    {   // Blattknoten
        Leaf leaf = Leaf::fromExisting(*(this->tmpPage), context);
        index_iterator it = leaf.lookupRange(lower, upper);
        //Leafs unlocken sich selber
        this->tmpPage = NULL;
        return it;
    }
    else
    {   // Innerer Knoten
        InnerNode innerNode = InnerNode::fromExisting(*(this->tmpPage), context);
        index_iterator it = innerNode.lookupRange(lower, upper);
        context.segment.unfixPage(*(this->tmpPage), false);
        this->tmpPage = NULL;
        return it;
    }
}

void Node::visualize(IndexVisualizer& vis)
{
    // Page mit Lesesperre öffnen
    this->tmpPage = &(context.segment.getPage(this->segmentPageId, false));
    NodeHeader& header = *((NodeHeader*)((*(this->tmpPage)).getData()));

    if (header.isLeaf)
    {   // Blattknoten
        Leaf leaf = Leaf::fromExisting(*(this->tmpPage), context);
        leaf.visualize(vis, segmentPageId);
    }
    else
    {   // Innerer Knoten
        InnerNode innerNode = InnerNode::fromExisting(*(this->tmpPage), context);
        innerNode.visualize(vis, segmentPageId);
    }
    context.segment.unfixPage(*(this->tmpPage), false);
    this->tmpPage = NULL;
}

void Node::redirectNext(unsigned newNeighbour, bool lastOneDeleted)
{   // Page mit Schreibsperre öffnen
    this->tmpPage = &(context.segment.getPage(this->segmentPageId, true));
    NodeHeader& header = *((NodeHeader*)((*(this->tmpPage)).getData()));

    if (header.isLeaf)
    {   // Blattknoten - hier wird umgelegt
        header.hasNext = header.hasNext && !lastOneDeleted;
        header.nextPageId = newNeighbour;
    }

    else
    {   // Innerer Knoten - hier wird nur weiterpropagiert
        InnerNode innerNode = InnerNode::fromExisting(*(this->tmpPage), context);
        innerNode.redirectNext(newNeighbour, lastOneDeleted);
    }
    context.segment.unfixPage(*(this->tmpPage), header.isLeaf);
    this->tmpPage = NULL;
}


/**
 * Hebt den Schreibschutz auf die von diesem Knoten repräsentierte Seite auf und propagiert den Unlock nach oben weiter.
 */
void Node::releaseWriteLock()
{
//    if (this->tmpPage==NULL) return;
//
//    context.segment.unfixPage(*(this->tmpPage), false);
//    this->tmpPage = NULL;
//
//    // Freigabe nach oben weiterpropagieren, anschließend den "freien" Parent vergessen
//    this->releaseParentsWriteLock();
}


/**
 * Hebt den Schreibschutz auf alle Vorgängerknoten auf. Der oberste schreibgeschützte Knoten ist nun dieser Knoten.
 */
void Node::releaseParentsWriteLock()
{
    //if (this->parent==NULL) return;
    //this->parent->releaseWriteLock();
    //this->parent = NULL;
}
